﻿using System;

namespace TypeBuilderFx.Core
{
    /// <summary>Base class for the emitters of types supporting a base class and exposing one interface.</summary>
    public abstract class SupportBaseClassTypeEmitterOptions : TypeEmitterOptions
    {
        private Type _baseType = typeof(object);
        private Type[] _baseConstructorParameterTypeList = new Type[0];

        /// <summary>Default constructor.  Initialize default values.</summary>
        public SupportBaseClassTypeEmitterOptions()
        {
            IsUsingBaseClassInterfaces = true;
            IsUsingBaseClassMembers = true;
        }

        /// <summary>The base type the emitted type is going to derive from.</summary>
        /// <remarks>Default is <see cref="object"/>.</remarks>
        public Type BaseType
        {
            get { return _baseType; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                _baseType = value;
            }
        }

        /// <summary>Parameter type list of base class.</summary>
        /// <remarks>Default is an empty list of types.</remarks>
        public Type[] BaseConstructorParameterTypeList
        {
            get { return _baseConstructorParameterTypeList; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                _baseConstructorParameterTypeList = value;
            }
        }

        /// <summary>
        /// Switch indicating if the type emitter should implement an interface if it is already implemented
        /// by its base class.
        /// </summary>
        /// <remarks>Default is <c>true</c>.</remarks>
        public bool IsUsingBaseClassInterfaces { get; set; }

        /// <summary>
        /// Switch indicating if the type emitter should implement a method / property / event if the base class
        /// already has one (public or protected) with the same name.
        /// </summary>
        /// <remarks>
        /// <para>Default is <c>true</c>.</para>
        /// <para>Useful if <see cref="UseBaseClassInterfaces"/> is <c>false</c>, the emitter will then go name by name.</para>
        /// <para>Also useful if the base class does implement members without a reference to an interface.</para>
        /// </remarks>
        public bool IsUsingBaseClassMembers { get; set; }

        #region object Methods
        /// <summary>Must be overriden by derived classes in order to be able to use as a key in an dictionary.</summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            int parameterTypeHashCode = 0;

            foreach (Type parameterType in _baseConstructorParameterTypeList)
            {
                parameterTypeHashCode ^= parameterType.GetHashCode();
            }

            return base.GetHashCode()
                ^ BaseType.GetHashCode()
                ^ parameterTypeHashCode
                ^ IsUsingBaseClassInterfaces.GetHashCode()
                ^ IsUsingBaseClassMembers.GetHashCode();
        }

        /// <summary>Must be overriden by derived classes in order to be able to use as a key in an dictionary.</summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            SupportBaseClassTypeEmitterOptions options = obj as SupportBaseClassTypeEmitterOptions;

            return base.Equals(obj)
                && options != null
                && options.BaseType == BaseType
                && AreEqual(options._baseConstructorParameterTypeList, _baseConstructorParameterTypeList)
                && options.IsUsingBaseClassInterfaces == IsUsingBaseClassInterfaces
                && options.IsUsingBaseClassMembers == IsUsingBaseClassMembers;
        }
        #endregion

        /// <summary>
        /// Validates the base type can't be <c>null</c>, must be a non-sealed class nor can any of the parameter type
        /// (if there is any).
        /// </summary>
        public override void Validate()
        {
            base.Validate();

            if (!BaseType.IsClass)
            {
                throw new ArgumentException("Isn't a class", "BaseType");
            }
            if (BaseType.IsSealed)
            {
                throw new ArgumentException("Is sealed (can't be a base class)", "BaseType");
            }
            foreach (Type parameterType in _baseConstructorParameterTypeList)
            {
                if (parameterType == null)
                {
                    throw new ArgumentNullException("BaseConstructorParameterTypeList", "One of the parameter type is null");
                }
            }
        }

        private bool AreEqual(Type[] typeListA, Type[] typeListB)
        {
            if (typeListA.Length != typeListB.Length)
            {
                return false;
            }
            else
            {
                for (int i = 0; i != typeListA.Length; ++i)
                {
                    if (typeListA[i] != typeListB[i])
                    {
                        return false;
                    }
                }

                return true;
            }
        }
    }
}